/*
*  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
*  reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*  1. Redistributions of source code must retain the above copyright
*  notice, this list of conditions and the following disclaimer.
*
*  2. Redistributions in binary form must reproduce the above copyright
*  notice, this list of conditions and the following disclaimer in
*  the documentation and/or other materials provided with the
*  distribution.
*
*  3. The end-user documentation included with the redistribution,
*  if any, must include the following acknowledgment:
*  "This product includes software developed by the
*  Sun Microsystems, Inc. for Project JXTA."
*  Alternately, this acknowledgment may appear in the software itself,
*  if and wherever such third-party acknowledgments normally appear.
*
*  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
*  must not be used to endorse or promote products derived from this
*  software without prior written permission. For written
*  permission, please contact Project JXTA at http://www.jxta.org.
*
*  5. Products derived from this software may not be called "JXTA",
*  nor may "JXTA" appear in their name, without prior written
*  permission of Sun.
*
*  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
*  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
*  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
*  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
*  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*  SUCH DAMAGE.
*  ====================================================================
*
*  This software consists of voluntary contributions made by many
*  individuals on behalf of Project JXTA.  For more
*  information on Project JXTA, please see
*  <http://www.jxta.org/>.
*
*  This license is based on the BSD license adopted by the Apache Foundation.
*
*  $Id: ChatDialogView.java,v 1.1 2007/06/10 21:21:51 nano Exp $
*/

package net.jxta.myjxta.ui;

import info.clearthought.layout.TableLayout;
import net.jxta.logging.Logging;
import net.jxta.myjxta.View;
import net.jxta.myjxta.dialog.Dialog;
import net.jxta.myjxta.dialog.DialogListener;
import net.jxta.myjxta.dialog.DialogMessage;
import net.jxta.myjxta.plugin.PluginView;
import net.jxta.myjxta.util.Constants;
import net.jxta.myjxta.util.Resources;
import net.jxta.myjxta.util.exec.ExecFactory;
import net.jxta.pipe.PipeService;
import org.jdesktop.swingx.JXEditorPane;
import org.jdesktop.swingx.action.ActionFactory;
import org.jdesktop.swingx.action.ActionManager;
import org.jdesktop.swingx.action.BoundAction;

import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.*;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.*;
import java.awt.event.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author james todd [gonzo at jxta dot org]
 * @author mike mcangus [mcangus at jxta dot org]
 * @version $Id: ChatDialogView.java,v 1.1 2007/06/10 21:21:51 nano Exp $
 */

public final class ChatDialogView
        extends JPanel
        implements DialogListener, PluginView {

    private static final String DEFAULT_LOG_PREFIX = "myjxta-";
    private static final String DEFAULT_LOG_SUFFIX = ".html";
    private static final String LOG_TIMESTAMP_FORMAT = "yyyy.MM.dd-HH.mm.ssZ";
    private static final int BLOCK = 4 * 1024;
    private static final int MAX_FONT_SIZE = 48;
    private static final int MIN_FONT_SIZE = 1;
    private static final int HISTORY_MAX = 15;
    private static final ResourceBundle STRINGS = Resources.getStrings();

    static final Logger LOG = Logger.getLogger(ChatDialogView.class.getName());
    static final List<String> imageTypes;

    Dialog dialog = null;
    JButton send = null;
    JXEditorPane editor = null;
    JPopupMenu editorPopup = null;
    JXEditorPane renderer = null;
    JPopupMenu rendererPopup = null;

    private ActionManager manager = null;
    private ArrayList<String> rendererActions = null;
    private ArrayList<String> editorActions = null;
    private ArrayList<StyledEditorKit.ForegroundAction> colorActions = null;
    private JScrollPane rendererScroller = null;
    private JScrollPane editorScroller = null;
    private StyleSheet style = null;
    private SimpleDateFormat logFormatter = null;
    private View view = null;
    private MyDropTargetListener dropper = null;
    private Font rendererDefaultFont = null;
    private Font editorDefaultFont = null;
    private List<String> history = null;
    private int historyIndex = 0;

    static {
        imageTypes = new ArrayList<String>();

        imageTypes.add(UIConstants.IMAGE_TIFF);
        imageTypes.add(UIConstants.IMAGE_TIF);
        imageTypes.add(UIConstants.IMAGE_GIF);
        imageTypes.add(UIConstants.IMAGE_JPEG);
        imageTypes.add(UIConstants.IMAGE_JPG);
        imageTypes.add(UIConstants.IMAGE_PNG);
    }

    /**
     * Creates a new ChatDialogView for the specified dialog within the specified view.
     *
     * @param view   the view (JFrame) within which this Dialog Panel will exist
     * @param dialog The Dialog within the view in which this Panel will exist.
     */
    public ChatDialogView(View view, Dialog dialog) {
        super();
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin ChatDialogView(View, Dialog) Constructor");
        }

        this.view = view;
        this.dialog = dialog;

        this.dialog.addListener(this);

        init();
        setEditor(false);
        if (this.dialog != null) {
            updateConnectionState();
        }
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   ChatDialogView(View, Dialog) Constructor");
        }
    }

    /**
     * TODO: Add documentation
     *
     * @param msg
     */
    public void receive(DialogMessage msg) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin receive(DialogMessage)");
            LOG.fine("msg = " + msg.getHtmlMessage());
        }

        final String m = msg.getHtmlMessage();
        if (m != null &&
                m.length() > 0) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    appendText(renderer, m);
                }
            });
        }

        this.view.updatePluginPanel(this, msg.getLabel());
        if (!this.getDialog().getPipeAdvertisement().getType().equals(PipeService.PropagateType)) {
            //1:1 chat goodbye
            if (Dialog.GOODBYE_CMD.equals(msg.getCommand())) {
                //the oposide party has closed the communication channel
                //give the user some feedback that it makes no sense to send a message --> disable the send button
                send.setEnabled(false);
                editor.setEnabled(false);
            }
        } else {
            //groupchat goodbye.... not sure what we should do here
            //nothing for now (maybe a rosterupdate later)
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   receive(DialogMessage)");
        }
    }

    /**
     * Returns the Dialog within which this ChatDialogView exists.
     *
     * @return The Dialog within which this ChatDialogView exists.
     */
    public Dialog getDialog() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In getDialog()");
        }

        return this.dialog;
    }

    /**
     * Removes this ChatDialogView from the dialog and view.
     */
    public void dismiss() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In dismiss()");
        }
        dialog.close(); //removeListener(this);
    }

    /**
     * Sets the focus in the main window to the Editor.
     */
    public void setFocus() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In setFocus()");
        }

        this.editor.requestFocus();
    }

    /**
     * TODO: Add documentation
     */
    // xxx: it would be nice to not make these public ... delegate if possible
    public void saveAsDialog() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin saveAsDialog()");
        }

        JFileChooser fc = new JFileChooser();

        fc.setSelectedFile(getDefaultLogFile(fc.getCurrentDirectory()));

        // xxx: ugly cast
        if (fc.showSaveDialog((Frame) view) ==
                JFileChooser.APPROVE_OPTION) {
            File f = fc.getSelectedFile();

            if (f.isDirectory()) {
                f = getDefaultLogFile(f);
            }

            File p = f.getParentFile();

            if (!p.exists()) {
                p.mkdirs();
            }

            FileWriter w = null;
            String msg = STRINGS.getString("status.dialog.save") + " to " +
                    f.getName();

            try {
                w = new FileWriter(f, true);
            } catch (IOException ioe) {
                msg = STRINGS.getString("error.dialog.save") + " " +
                        f.getName();
            }

            if (w != null) {
                try {
                    f.createNewFile();

                    StringReader r = new StringReader(readText(renderer));
                    char[] buf = new char[BLOCK];
                    int l;

                    while ((l = r.read(buf, 0, BLOCK)) > -1) {
                        w.write(buf, 0, l);
                    }
                } catch (IOException ioe) {
                    msg = STRINGS.getString("error.dialog.save") + " " +
                            f.getName();
                } finally {
                    try {
                        w.close();
                    } catch (IOException ioe) {
                        if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                            LOG.log(Level.SEVERE, "Caught unexpected Exception",
                                    ioe);
                        }
                    }
                }
                this.view.setStatus(msg);
            }
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   saveAsDialog()");
        }
    }

    /**
     * Clears the contents of this dialog's display.
     */
    public void clearDialog() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In clearDialog()");
        }

        removeText(this.renderer);
    }

    /**
     * TODO: Add documentation
     *
     * @param selected
     */
    public void setEditor(boolean selected) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In setEditor(boolean)");
        }

        setEditor(selected, false);
    }

    /**
     * TODO: Add documentation
     *
     * @param selected
     * @param reset
     */
    public void setEditor(boolean selected, boolean reset) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin setEditor(boolean, boolean)");
            LOG.fine("selected = " + selected);
            LOG.fine("reset    = " + reset);
        }

        final String contentType = !selected ?
                UIConstants.MIME_HTML : UIConstants.MIME_PLAIN;
        String t = (!reset && this.editor != null) ? removeText(this.editor) : "";

        this.editor = new JXEditorPane();

        this.editor.setContentType(contentType);

        if (contentType.equalsIgnoreCase(UIConstants.MIME_HTML) &&
                this.style != null) {
            StyleSheet ds = ((HTMLDocument) this.editor.getDocument()).getStyleSheet();

            if (ds != null) {
                ds.addStyleSheet(this.style);
            } else {
                ds = this.style;
            }

            ((HTMLEditorKit) this.editor.getEditorKit()).setStyleSheet(ds);
        }

//        this.editor.setPreferredSize(new Dimension(UIConstants.EDITOR_WIDTH,
//            UIConstants.EDITOR_HEIGHT));
        this.editor.setMargin(new Insets(3, 3, 3, 3));
        this.editor.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent me) {
                showPopupMenu(me);
            }

            public void mouseReleased(MouseEvent me) {
                showPopupMenu(me);
            }

            private void showPopupMenu(MouseEvent me) {
                if (me.isPopupTrigger() &&
                        me.getSource() == editor) {
                    editorPopup.show(editor, me.getX(), me.getY());
                }
            }
        });
        this.editor.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent ke) {
                if (ke.isControlDown()) {
                    if (ke.getKeyCode() == KeyEvent.VK_UP ||
                            ke.getKeyCode() == KeyEvent.VK_DOWN) {
                        ke.consume();

                        if (ke.getKeyCode() == KeyEvent.VK_UP &&
                                historyIndex < HISTORY_MAX - 1 &&
                                historyIndex < history.size() - 1) {
                            if (historyIndex == 0) {
                                addHistory(removeText(editor));
                            }

                            setText(editor, history.get(++historyIndex));
                        } else if (ke.getKeyCode() == KeyEvent.VK_DOWN &&
                                historyIndex > 0) {

                            setText(editor, history.get(--historyIndex));
                        }
                    } else {
                        Font f;
                        int i;

                        switch (ke.getKeyCode()) {
                            case KeyEvent.VK_0:
                                setFont(editorDefaultFont);

                                break;
                            case KeyEvent.VK_EQUALS:
                                f = getFont();
                                i = f.getSize();

                                setFont(new Font(f.getName(), f.getStyle(),
                                        Math.min(++i, MAX_FONT_SIZE)));

                                break;
                            case KeyEvent.VK_MINUS:
                                f = getFont();
                                i = f.getSize();

                                setFont(new Font(f.getName(), f.getStyle(),
                                        Math.max(--i, MIN_FONT_SIZE)));

                                break;
                            default:
                        }
                    }
                } else {
                    if (ke.getKeyCode() == KeyEvent.VK_ENTER &&
                            contentType.equals(UIConstants.MIME_HTML)) {
                        ke.consume();
                        send.getAction().actionPerformed(null);
                    }
                }
            }

            private Font getFont() {
                return editor.getFont();
            }

            private void setFont(Font f) {
                editor.setFont(f);
            }
        });
        this.editor.getActionMap().put(DefaultEditorKit.pasteAction,
                new AbstractAction() {

                    public Object getValue(String key) {
                        Object o = super.getValue(key);

                        if (Logging.SHOW_FINE &&
                                LOG.isLoggable(Level.FINE)) {
                            LOG.fine("In setEditor#AbstractAction.getValue(String)");
                            LOG.fine("Key = \"" + key +
                                    "\", Value = \"" + o +
                                    "\"");
                        }
                        return UIConstants.PRE_PREFIX + o +
                                UIConstants.PRE_POSTFIX;
                    }

                    public void actionPerformed(ActionEvent ae) {
                        if (Logging.SHOW_FINE &&
                                LOG.isLoggable(Level.FINE)) {
                            LOG.fine("In setEditor#AbstractAction.actionPerformed(ActionEvent)");
                        }
                        Clipboard clipboard = getToolkit().getSystemClipboard();
                        Transferable content = clipboard.getContents(this);

                        dropper.handleExternalDataInput(new PastedExternalObject(content));
                    }
                });
        this.editor.setCaretPosition(this.editor.getDocument().getLength());

        this.editorDefaultFont = this.editor.getFont();

        InputMap im = this.editor.getInputMap();

        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_B, Event.CTRL_MASK),
                DefaultEditorKit.backwardAction);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.CTRL_MASK),
                DefaultEditorKit.forwardAction);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_P, Event.CTRL_MASK),
                DefaultEditorKit.upAction);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_N, Event.CTRL_MASK),
                DefaultEditorKit.downAction);
        im.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, Event.CTRL_MASK),
                UIConstants.BREAK);

        new DropTarget(this.editor, this.dropper);

        setText(this.editor, t);

        float lineHeight = editor.getFontMetrics(editor.getFont()).getLineMetrics("A", 0, 1, this.getGraphics()).getHeight();
        int prefHeight = (int) (2 * (Math.floor(lineHeight) + 1));
        int prefWidth = editor.getPreferredSize().width;
        this.editor.setPreferredSize(new Dimension(prefWidth, prefHeight));

        this.editorScroller.setViewportView(this.editor);

        this.manager.setEnabled(UIConstants.BOLD, !selected);
        this.manager.setEnabled(UIConstants.ITALIC, !selected);
        this.manager.setEnabled(UIConstants.UNDERLINE, !selected);
        this.manager.setEnabled(UIConstants.LEFT, !selected);
        this.manager.setEnabled(UIConstants.CENTER, !selected);
        this.manager.setEnabled(UIConstants.RIGHT, !selected);
        this.manager.setSelected(UIConstants.EDITOR, selected);

        //if (reset) {
        // xxx: don't reset but honor currently selected
        //this.manager.setSelected(Constants.LEFT, true);
        //}

        this.historyIndex = 0;

        setFocus();

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   setEditor(boolean, boolean)");
        }
    }

    /**
     * Init Components and layout panel
     *
     * @modified 2005-04-24 jamoore added TableLayout for better resize handling
     */
    private void init() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin init()");
        }

        this.manager = createManager();
        this.dropper = createDropper();
        this.logFormatter = new SimpleDateFormat(LOG_TIMESTAMP_FORMAT);
        this.style = createStyle();
        /*
        GridBagLayout gb = new GridBagLayout();
        GridBagConstraints gbc = new GridBagConstraints();

        setLayout(gb);
//        setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));

        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.insets = new Insets(3, 3, 3, 3);
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.weightx = 1.0;
        gbc.weighty = 1.0;
        gbc.fill = GridBagConstraints.BOTH;
        
        Component c = createRenderer();

        gb.setConstraints(c, gbc);

        add(c);

        c = createEditor();

        gbc.gridy++;
        gbc.weighty = 0.0;
        gbc.fill = GridBagConstraints.HORIZONTAL;

        gb.setConstraints(c, gbc);

        add(c);
        */
        double blank = 5;
        double sizes[][] = {{blank, TableLayout.FILL, blank},
                {blank, .75, blank, TableLayout.MINIMUM, blank, .25}};

        TableLayout tbl = new TableLayout(sizes);

        setLayout(tbl);

        Component c = createRenderer();
        add(c, "1,1");

        c = createEditor();

        add(c, "1,5");

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   init()");
        }
    }

    private ActionManager createManager() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin createManager()");
        }

        ActionManager am = ActionManager.getInstance();
        String action;
        String resource;

        this.rendererActions = new ArrayList<String>();

        // xxx: shouldn't have to set toggle state
        this.rendererActions.add(action = UIConstants.SAVE_AS);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_SAVE_AS),
                getImagePath(UIConstants.SAVE_AS_IMAGE), resource, null,
                UIConstants.SAVE_AS_MNEMONIC, false, true, this, "saveAsDialog"));
        this.rendererActions.add(action = UIConstants.CLEAR);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_CLEAR),
                null, resource, null, UIConstants.CLEAR_MNEMONIC, false, true, this,
                "clearDialog"));

        this.editorActions = new ArrayList<String>();

        //todo: remvoe the false here if we have a working preference system to determine if the user
        //whants em
        if (false) { // remove the undo/redo buttons until we can reenable em via preferences
            this.editorActions.add(action = UIConstants.UNDO);
            am.addAction(createAction(action,
                    resource = STRINGS.getString(UIConstants.ACTION_UNDO),
                    getImagePath(UIConstants.UNDO_IMAGE), resource, null,
                    UIConstants.UNDO_MNEMONIC, false, false));
            this.editorActions.add(action = UIConstants.REDO);
            am.addAction(createAction(action,
                    resource = STRINGS.getString(UIConstants.ACTION_REDO),
                    getImagePath(UIConstants.REDO_IMAGE), resource, null,
                    UIConstants.REDO_MNEMONIC, false, false));

            this.editorActions.add(null);
        }

        this.editorActions.add(action = UIConstants.BOLD);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_BOLD),
                getImagePath(UIConstants.BOLD_IMAGE), resource, null,
                UIConstants.BOLD_MNEMONIC, true, true));
        this.editorActions.add(action = UIConstants.ITALIC);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_ITALIC),
                getImagePath(UIConstants.ITALIC_IMAGE), resource, null,
                UIConstants.ITALIC_MNEMONIC, true, true));
        this.editorActions.add(action = UIConstants.UNDERLINE);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_UNDERLINE),
                getImagePath(UIConstants.UNDERLINE_IMAGE), resource, null,
                UIConstants.UNDERLINE_MNEMONIC, true, true));

        this.editorActions.add(null);
        this.editorActions.add(action = UIConstants.CUT);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_CUT),
                getImagePath(UIConstants.CUT_IMAGE), resource, null,
                UIConstants.CUT_MNEMONIC, false, true));
        this.editorActions.add(action = UIConstants.COPY);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_COPY),
                getImagePath(UIConstants.COPY_IMAGE), resource, null,
                UIConstants.COPY_MNEMONIC, false, true));
        this.editorActions.add(action = UIConstants.PASTE);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_PASTE),
                getImagePath(UIConstants.PASTE_IMAGE), resource, null,
                UIConstants.PASTE_MNEMONIC, false, true));

        this.editorActions.add(null);
        this.editorActions.add(action = UIConstants.LEFT);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_LEFT),
                getImagePath(UIConstants.LEFT_IMAGE), resource,
                UIConstants.PARAGRAPH, UIConstants.LEFT_MNEMONIC, true, true));
        this.editorActions.add(action = UIConstants.CENTER);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_CENTER),
                getImagePath(UIConstants.CENTER_IMAGE), resource,
                UIConstants.PARAGRAPH, UIConstants.CENTER_MNEMONIC, true, true));
        this.editorActions.add(action = UIConstants.RIGHT);
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_RIGHT),
                getImagePath(UIConstants.RIGHT_IMAGE), resource,
                UIConstants.PARAGRAPH, UIConstants.RIGHT_MNEMONIC, true, true));

        action = UIConstants.SELECT_ALL;
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_SELECT_ALL),
                null, resource, null, UIConstants.SELECT_ALL_MNEMONIC, false, true));

        action = UIConstants.EDITOR;
        am.addAction(createAction(action,
                resource = STRINGS.getString(UIConstants.ACTION_EDITOR),
                getImagePath(UIConstants.EDITOR_IMAGE), resource, null,
                UIConstants.EDITOR_MNEMONIC, true, true, this, "setEditor"));

        this.colorActions = new ArrayList<StyledEditorKit.ForegroundAction>();

        resource = STRINGS.getString("label.color.red");
        this.colorActions.add(
                new StyledEditorKit.ForegroundAction(resource, Color.red));
        resource = STRINGS.getString("label.color.green");
        this.colorActions.add(
                new StyledEditorKit.ForegroundAction(resource, Color.green));
        resource = STRINGS.getString("label.color.blue");
        this.colorActions.add(
                new StyledEditorKit.ForegroundAction(resource, Color.blue));
        resource = STRINGS.getString("label.color.black");
        this.colorActions.add(
                new StyledEditorKit.ForegroundAction(resource, Color.black));

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   createManager()");
        }

        return am;
    }

    private String getImagePath(String name) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In getImagePath(String)");
        }

        return getImagePath(name, UIConstants.IMAGE_SIZE_SMALL);
    }

    private String getImagePath(String name, int size) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In getImagePath(String, int)");
        }

        return getImagePath(name, size, UIConstants.MIME_GIF);
    }

    private String getImagePath(String name, int size, String type) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In getImagePath(String, int, String)");
            LOG.fine("name = " + name);
            LOG.fine("size = " + size);
            LOG.fine("type = " + type);
        }

        return name + size + type;
    }

    private Component createRenderer() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin createRenderer()");
        }

        add(this.rendererPopup = createRendererPopup(), "0,0");

        JPanel p = new JPanel();
        GridBagLayout gb = new GridBagLayout();

        p.setLayout(gb);
        //p.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));

        this.renderer = new JXEditorPane();

        this.renderer.setContentType(UIConstants.MIME_HTML);

        if (this.style != null) {
            StyleSheet ds = ((HTMLDocument) this.renderer.getDocument()).getStyleSheet();

            if (ds != null) {
                ds.addStyleSheet(this.style);
            } else {
                ds = this.style;
            }

            ((HTMLEditorKit) this.renderer.getEditorKit()).setStyleSheet(ds);
        }

//        this.renderer.setPreferredSize(new Dimension(UIConstants.RENDERER_WIDTH,
//            UIConstants.RENDERER_HEIGHT));
        this.renderer.setMinimumSize(this.renderer.getPreferredSize());
        this.renderer.setMargin(new Insets(3, 3, 3, 3));
        this.renderer.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent me) {
                showPopupMenu(me);
            }

            public void mouseReleased(MouseEvent me) {
                showPopupMenu(me);
            }

            private void showPopupMenu(MouseEvent me) {
                if (me.isPopupTrigger() &&
                        me.getSource() == renderer) {
                    rendererPopup.show(renderer, me.getX(), me.getY());
                }
            }
        });
        this.renderer.addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent ke) {
                if (ke.isControlDown()) {
                    Font f;

                    switch (ke.getKeyCode()) {
                        case KeyEvent.VK_0:
                            setFont(rendererDefaultFont);

                            break;
                        case KeyEvent.VK_EQUALS:
                            f = getFont();

                            setFont(new Font(f.getName(), f.getStyle(),
                                    f.getSize() + 1));

                            break;
                        case KeyEvent.VK_MINUS:
                            f = getFont();

                            setFont(new Font(f.getName(), f.getStyle(),
                                    f.getSize() - 1));

                            break;
                        default:
                    }
                }
            }

            private Font getFont() {
                return renderer.getFont();
            }

            private void setFont(Font f) {
                renderer.setFont(f);
            }
        });
        this.renderer.setEditable(false);
        this.renderer.addHyperlinkListener(new HyperlinkListener() {
            public void hyperlinkUpdate(HyperlinkEvent he) {
                if (he.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
                    final URL url = he.getURL();
                    if (url != null)
                        ExecFactory.getExec().execDocument(url);
                }
            }
        });

        this.rendererDefaultFont = this.renderer.getFont();

        this.rendererScroller = new JScrollPane(this.renderer,
                JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);

//        this.rendererScroller.setMinimumSize(this.renderer.getPreferredSize());
        this.rendererScroller.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);

        GridBagConstraints gbc = new GridBagConstraints();

        gbc.gridx = 0;
        gbc.gridy = 0;
        gbc.gridwidth = GridBagConstraints.REMAINDER;
//        gbc.insets = new Insets(3, 3, 3, 3);
        gbc.anchor = GridBagConstraints.NORTHWEST;
        gbc.weightx = 1.0;
        gbc.weighty = 1.0;
        gbc.fill = GridBagConstraints.BOTH;

        gb.setConstraints(this.rendererScroller, gbc);

        p.add(this.rendererScroller);

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   createRenderer()");
        }

        return p;
    }

    private JPopupMenu createRendererPopup() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In createRendererPopup()");
        }

        List<String> l = new ArrayList<String>(this.rendererActions);

        return manager.getFactory().createPopup(l);
    }


    /**
     * Creates a panel for the editor and its related components.
     * Added TableLayout for better handling when ChatDialogView is resized.
     * TableLayout does a slightly better job than GridBagLayout however
     * the constrainsts based appoach alone is not sufficent. A rules engine
     * would allow for specific case component adjustment.
     * <p/>
     * ie:  the editor panel is given the constraint of 25%. This is fine for
     * maximized or normal sized ChatDialogView. When the ChatDialogView size
     * goes below 200 the editor window is no longer large enuff to display
     * readable characters.  Even anonymous subclassing with the addition of
     * getMinimumSize() seems to be ignored. Possible Solution: Along with
     * the normal FILL contraints for the editor check for a rule of
     * <p/>
     * if [getSize().height < 200]
     * use components minimum size
     * (instead of the resize constraint of 25% )
     * fi
     *
     * @modified 2005-04-24 jamoore added TableLayout for better resize handling
     */
    private Component createEditor() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin createEditor()");
        }

        add(this.editorPopup = createEditorPopup(), "0,0");

        JToolBar tb = this.manager.getFactory().createToolBar(this.editorActions);

        tb.setFloatable(false);


        double sizes[][] = {{TableLayout.FILL, 5, TableLayout.PREFERRED}, {TableLayout.FILL}};

        TableLayout editorLayout = new TableLayout(sizes);

        JPanel p = new JPanel(editorLayout);

        this.add(tb, "1, 3");

        this.editorScroller = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
                JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);

        this.editorScroller.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);

        p.add(editorScroller, "0, 0");

        this.send = new JButton(new AbstractAction() {
            public void actionPerformed(ActionEvent ae) {
                if (send.isEnabled()) {
                    dispatch();
                }
            }
        });

        this.send.setText(STRINGS.getString(UIConstants.ACTION_SEND));
        this.send.setEnabled(false);
        this.send.addKeyListener(new AbstractButtonKeyListener(this.send) {
            public void keyPressed(KeyEvent ke) {
                getButton().getAction().actionPerformed(null);
            }
        });

        p.add(this.send, "2, 0,c,c");

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   createEditor()");
        }

        return p;
    }

    private JPopupMenu createEditorPopup() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin createEditorPopup()");
        }

        List<String> l = new ArrayList<String>(this.editorActions);

        l.add(null);
        l.add(UIConstants.SELECT_ALL);
        l.add(null);
        l.add(UIConstants.EDITOR);

        JPopupMenu pm = manager.getFactory().createPopup(l);
        JMenu cm = new JMenu(STRINGS.getString("label.color"));

        for (StyledEditorKit.ForegroundAction colorAction : colorActions) {
            cm.add(colorAction);
        }

        pm.add(cm);

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   createEditorPopup()");
        }

        return pm;
    }

    private Action createAction(String id, String name, String icon,
                                String desc, String group, String mnemonic, boolean toggle,
                                boolean isEnabled) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In createAction(String, String, String, String, String, String, boolean, boolean)");
        }

        return createAction(id, name, icon, desc, group, mnemonic, toggle,
                isEnabled, null, null);
    }

    private Action createAction(String id, String name, String icon,
                                String desc, String group, String mnemonic, boolean toggle,
                                boolean isEnabled, Object callback, String handler) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin createAction(String, String, String, String, String, String, boolean, boolean, Object, String)");
            LOG.fine("id        = " + id);
            LOG.fine("name      = " + name);
            LOG.fine("icon      = " + icon);
            LOG.fine("desc      = " + desc);
            LOG.fine("group     = " + group);
            LOG.fine("mnemonic  = " + mnemonic);
            LOG.fine("toggle    = " + toggle);
            LOG.fine("isEnabled = " + isEnabled);
            LOG.fine("callback  = " + callback);
            LOG.fine("handler   = " + handler);
        }

        AbstractAction a = createAction(id, name, mnemonic, toggle, group,
                callback, handler);

        ImageIcon ii = null;
        if (icon != null) {
            try {
                ii = new ImageIcon(getClass().getResource(icon));
            } catch (Exception e) {
                LOG.info("failed to create icon for \'" + icon + "\'.");
            }
        }

        ActionFactory.decorateAction(a, desc, desc, ii, ii, null);

        if (callback != null &&
                handler != null &&
                handler.trim().length() > 0) {
            ((BoundAction) a).registerCallback(callback, handler);
        }

        a.setEnabled(isEnabled);

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   createAction(String, String, String, String, String, String, boolean, boolean, Object, String)");
        }

        return a;
    }

    private AbstractAction createAction(String id, String name,
                                        String mnemonic, boolean toggle, String group, Object callback,
                                        String handler) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin createAction(String, String, String, boolean, String, Object, String)");
            LOG.fine("id        = " + id);
            LOG.fine("name      = " + name);
            LOG.fine("mnemonic  = " + mnemonic);
            LOG.fine("toggle    = " + toggle);
            LOG.fine("group     = " + group);
            LOG.fine("callback  = " + callback);
            LOG.fine("handler   = " + handler);
        }

        AbstractAction a;

        if (callback == null ||
                handler == null ||
                (handler.trim().length() == 0)) {
            a = ActionFactory.createTargetableAction(id, name, mnemonic,
                    toggle, group);
        } else {
            a = ActionFactory.createBoundAction(id, name, mnemonic, toggle,
                    group);
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   createAction(String, String, String, boolean, String, Object, String)");
        }

        return a;
    }

    // xxx: don't dispatch empty body ... but we'll loose headers (eg location)
    void dispatch() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin dispatch()");
        }

        setEditor(false);

        // xxx: need to dispatch if no content yet includes markup (eg <img ...>)
        if (hasBody(this.editor)) {
            String s = removeText(this.editor);

            this.dialog.dispatch(s);
            setEditor(false, true);
            addHistory(s);
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   dispatch()");
        }
    }

    private void addHistory(String s) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin addHistory(String)");
        }

        if (this.history == null) {
            this.history = new ArrayList<String>();
        }

        this.history.add(0, s);

        int i;

        while ((i = this.history.size()) > HISTORY_MAX) {
            this.history.remove(i - 1);
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   addHistory(String)");
        }
    }

    private File getDefaultLogFile(File parent) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In getDefaultLogFile(File)");
        }

        return new File(parent, DEFAULT_LOG_PREFIX +
                this.logFormatter.format(new Date()) + DEFAULT_LOG_SUFFIX);
    }

    private MyDropTargetListener createDropper() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin createDropper()");
        }

        MyDropTargetListener dtl = new MyDropTargetListener();

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   createDropper()");
        }

        return dtl;
    }

    private StyleSheet createStyle() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin createStyle()");
        }

        StyleSheet ss = null;
        Constants c = Constants.getInstance();
        String style = c.get(Constants.STYLE_RESOURCES, null);

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Constants.STYLE_RESOURCES = " + Constants.STYLE_RESOURCES);
            LOG.fine("StyleSheet = " + style);
        }

        if (style != null &&
                style.trim().length() > 0) {
            ss = new StyleSheet();

            ss.importStyleSheet(getClass().getResource(style));
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   createStyle()");
        }

        return ss;
    }

    private Point calculateScrollerPosition() {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin calculateScrollerPosition()");
        }

        JScrollBar vb = this.rendererScroller.getVerticalScrollBar();
        BoundedRangeModel m = vb != null ? vb.getModel() : null;

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   calculateScrollerPosition()");
        }

        return (m != null &&
                m.getValue() + m.getExtent() < m.getMaximum()) ?
                this.rendererScroller.getViewport().getViewPosition() : null;
    }

    void adjustScroller(Point p) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin adjustScroller()");
            LOG.fine("In adjustScroller(Point)");
        }

        if (p != null) {
            this.rendererScroller.getViewport().setViewPosition(p);
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   adjustScroller()");
        }
    }

    private Element getElement(Document d, HTML.Tag t) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In getElement(Document, HTML.Tag)");
        }

        return getElement(d != null ?
                d.getDefaultRootElement() : null, t);
    }

    private Element getElement(Element e, HTML.Tag t) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin getElement(Element, HTML.Tag)");
        }

        Element te = null;

        if (e != null) {
            Element be;

            for (int i = 0; i < e.getElementCount() && te == null; i++) {
                be = e.getElement(i);

                if (be.getName().equals(t.toString())) {
                    te = be;
                }

                if (te == null &&
                        !be.isLeaf()) {
                    te = getElement(be, t);
                }
            }
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   getElement(Element, HTML.Tag)");
        }

        return te;
    }

    private boolean hasBody(JXEditorPane editorPane) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin hasBody(JXEditorPane)");
        }

        Element bt = getElement(editorPane.getDocument(), HTML.Tag.BODY);
        HTMLDocument d = (HTMLDocument) bt.getDocument();
        String t = null;

        try {
            t = d.getText(0, d.getLength());
        } catch (BadLocationException ble) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", ble);
            }
        }

        boolean retVal = t != null && t.trim().length() > 0;

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Editor Pane contains " + t);
            LOG.fine("returning " + retVal);
            LOG.fine("End   hasBody(JXEditorPane)");
        }

        return retVal;
    }

    private String readText(JXEditorPane editorPane) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In readText(JXEditorPane)");
        }

        return readText(editorPane, 0, editorPane.getDocument().getLength());
    }

    private String readText(JXEditorPane editorPane, int start, int end) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin readText(JXEditorPane, int, int)");
        }

        Writer w = new StringWriter();

        try {
            editorPane.getEditorKit().write(w, editorPane.getDocument(), start, end);
        } catch (IOException ioe) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", ioe);
            }
        } catch (BadLocationException ble) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", ble);
            }
        }

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   readText(JXEditorPane, int, int)");
        }

        return w.toString();
    }

    private String setText(JXEditorPane editorPane, String s) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In setText(JXEditorPane, String)");
        }

        String t = removeText(editorPane);

        insertText(editorPane, s);

        return t;
    }

    void appendText(JXEditorPane editorPane, String s) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In appendText(JXEditorPane, String)");
        }

        if (editorPane != null &&
                editorPane.getDocument() != null) {
            insertText(editorPane, s, editorPane.getDocument().getLength());
        }
    }

    void insertText(JXEditorPane editorPane, String s) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In appendText(JXEditorPane, String)");
        }

        insertText(editorPane, s, editorPane.getCaretPosition());
    }

    private void insertText(JXEditorPane editorPane, String s, int index) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin insertText(JXEditorPane, String, int)");
            LOG.fine("String = \"" + s + "\"");
            LOG.fine("int = \"" + index + "\"");
        }

        EditorKit ek = editorPane.getEditorKit();
        Document d = editorPane.getDocument();

        if (d != null &&
                s != null &&
                s.length() > 0) {
            final Point p = calculateScrollerPosition();
            boolean isValid = false;

            try {
                if (ek instanceof HTMLEditorKit) {
                    ((HTMLEditorKit) ek).insertHTML((HTMLDocument) d, index, s,
                            0, 0, null);
                } else if (ek instanceof DefaultEditorKit) {
                    ek.read(new StringReader(s), d, index);
                }

                isValid = true;
            } catch (IOException ioe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", ioe);
                }
            } catch (BadLocationException ble) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", ble);
                }
            }

            if (isValid) {
                editorPane.setCaretPosition(Math.min(index + s.length(),
                        d.getLength()));

                EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        adjustScroller(p);
                    }
                });
            }

            update(editorPane);
        }
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   insertText(JXEditorPane, String, int)");
        }
    }

    private String removeText(JXEditorPane editorPane) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In removeText(JXEditorPane)");
        }

        return removeText(editorPane, 0, editorPane.getDocument().getLength());
    }

    private String removeText(JXEditorPane editorPane, int start, int end) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("Begin removeText(JXEditorPane, int, int)");
        }

        String s = readText(editorPane);
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("editorPane = " + s);
            LOG.fine("start      = " + start);
            LOG.fine("end        = " + end);
        }

        try {
            editorPane.getDocument().remove(start, end);
        } catch (BadLocationException ble) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", ble);
            }
        }

        update(editorPane);

        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("End   removeText(JXEditorPane, int, int)");
        }

        return s;
    }

    private void update(JXEditorPane editorPane) {
        if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
            LOG.fine("In update(JXEditorPane)");
        }

        editorPane.revalidate();
        editorPane.repaint();
    }

    public void updateConnectionState() {
        if (this.dialog != null && this.send != null) {
            boolean connected = this.dialog.isConnected();
            send.setEnabled(connected);
        }
    }

    /**
     * External Object wrappes the different Datasources (Dnd or Copy/Paste for now)
     * TODO: move this class hierachy to a separate Class (something like an own TransferHandler)
     */
    private interface ExternalObject {

        Object getData(DataFlavor p_dataFlavour) throws UnsupportedFlavorException;

        boolean isDataFlavorSupported(DataFlavor p_dataFlavour);


        Transferable getTransferable();
    }

    /**
     * Wrappes an external object generated by an Paste Event
     */
    private final class PastedExternalObject implements ExternalObject {
        private final Transferable m_transferable;

        public Transferable getTransferable() {
            return m_transferable;
        }

        public PastedExternalObject(Transferable p_transferableFromClipboard) {
            m_transferable = p_transferableFromClipboard;
        }

        public Object getData(DataFlavor p_dataFlavour) throws UnsupportedFlavorException {
            try {
                return m_transferable.getTransferData(p_dataFlavour);
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        public boolean isDataFlavorSupported(DataFlavor p_dataFlavour) {
            return m_transferable.isDataFlavorSupported(p_dataFlavour);
        }
    }

    /**
     * Wrappes an external object generated by a drop Event
     */
    private class DroppedExternalObject implements ExternalObject {
        private final DropTargetDropEvent m_dropEvent;

        public DroppedExternalObject(DropTargetDropEvent p_dropEvent) {
            m_dropEvent = p_dropEvent;
        }

        public Transferable getTransferable() {
            return m_dropEvent.getTransferable();
        }

        public Object getData(DataFlavor p_dataFlavour) throws UnsupportedFlavorException {
            try {
                return m_dropEvent.getTransferable().getTransferData(p_dataFlavour);
            }
            catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        public boolean isDataFlavorSupported(DataFlavor p_dataFlavour) {
            return m_dropEvent.isDataFlavorSupported(p_dataFlavour);
        }
    }

    private class MyDropTargetListener implements DropTargetListener {
        public void dragEnter(DropTargetDragEvent dtde) {
            dtde.acceptDrag(DnDConstants.ACTION_COPY);
        }

        public void dragExit(DropTargetEvent dte) {
            // Do nothing.  Needed for DropTargetListener Interface
        }

        public void dragOver(DropTargetDragEvent dtde) {
            // Do nothing.  Needed for DropTargetListener Interface
        }

        public void drop(DropTargetDropEvent dtde) {
            if (dtde.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                dtde.acceptDrop(DnDConstants.ACTION_COPY);
            } else if (dtde.isDataFlavorSupported(DataFlavor.stringFlavor)) {
                dtde.acceptDrop(DnDConstants.ACTION_COPY);
            }


            boolean isValid = handleExternalDataInput(new DroppedExternalObject(dtde));

            dtde.dropComplete(isValid);

            if (!isValid) {
                dtde.rejectDrop();
            }
        }

        public void dropActionChanged(DropTargetDragEvent dtde) {
            // Do nothing.  Needed for DropTargetListener Interface
        }


        /**
         * This one is doing the editor manipulation based on a already choosen dropped java object
         *
         * @param d
         * @return
         */
        public boolean handle(Object d) {
            boolean isValid = false;
            String s = null;

            if (d != null) {
                if (d instanceof Collection) {
                    for (Object o : ((Collection) d)) {
                        isValid = handle(o);
                    }
                } else if (d instanceof File) {
                    s = processFile((File) d);
                } else if (d instanceof String) {
                    s = processString((String) d);
                }
                if (s != null) {
                    insertText(editor, s);
                    isValid = true;
                } else {
                    isValid = false;
                }
            }
            return isValid;
        }

        private String processFile(File p_file) {
            try {
                URL url = p_file.toURL();
                return processURL(url);
            }
            catch (MalformedURLException e) {
                if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
                    LOG.log(Level.WARNING, "Caught unexpected Exception", e);
                }
            }
            return null;
        }

        private String processString(String d) {

            String s = null;
            if (d != null &&
                    d.trim().length() > 0) {
                URL u = null;

                try {
                    u = new URL(d);
                } catch (MalformedURLException mue) {
                    if (Logging.SHOW_WARNING && LOG.isLoggable(Level.WARNING)) {
                        LOG.log(Level.WARNING, "Caught unexpected Exception", mue);
                    }
                }

                if (u != null) {
                    s = processURL(u);
                } else {
                    s = decorateText(d);
                }
            }

            return s;
        }

        private String processURL(URL p_u) {
            String s = null;
            if (p_u.getProtocol().equalsIgnoreCase(UIConstants.PROTOCOL_FILE)) {

                try {
                    Object o = p_u.getContent();

                    //nano:
                    //right now we can only handle simple input streams
                    //dragged textfiles will end here as a SimpleTextInputStream
                    //maybe move this filehandling inside processFile
                    if (o instanceof InputStream) {
                        s = decorateText(readInputStream((InputStream) o));
                    }

                }
                catch (IOException e) {
                    e.printStackTrace();
                }


            } else if (p_u.getProtocol().equalsIgnoreCase(UIConstants.PROTOCOL_HTTP)) {
                //firefox url for example
                s = decorateHTML(p_u);
            }
            return s;
        }

        /**
         * Read all data from the given input Stream and returns it as a string
         *
         * @param o
         * @return
         */
        private String readInputStream(InputStream o) {
            StringBuffer sb = null;

            try {
                Reader r = new BufferedReader(new InputStreamReader(o));
                for (int c = r.read(); c >= 0; c = r.read()) {
                    if (sb == null) {
                        sb = new StringBuffer();
                    }

                    sb.append((char) c);
                }
            } catch (IOException ioe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", ioe);
                }
            }

            return sb != null ? sb.toString() : null;
        }

        private String decorateHTML(URL u) {
            String s = null;
            String t = u != null ? u.toString() : null;

            if (t != null) {
                if (isImage(u)) {
                    s = UIConstants.IMAGE_PREFIX + t +
                            UIConstants.IMAGE_POSTFIX;
                } else {
                    int i = t.indexOf(UIConstants.URI_DELIMITER);

                    s = UIConstants.ANCHOR_PREFIX + t +
                            UIConstants.ANCHOR_PREFIX_CLOSE +
                            ((i >= 0) ?
                                    t.substring(i + UIConstants.URI_DELIMITER.length()) : t) +
                            UIConstants.ANCHOR_POSTFIX;
                }
            }

            return s;
        }

        private boolean isImage(URL u) {
            String s = u != null ? u.getFile().toLowerCase() : null;
            int i = s.lastIndexOf(UIConstants.DOT);
            String t = i > -1 ? s.substring(i) : null;

            return imageTypes.contains(t);
        }

        private String decorateText(String s) {
            String t = s != null ? s.trim() : null;

            if (t != null &&
                    t.length() > 0) {
                t = UIConstants.PRE_PREFIX + t + UIConstants.PRE_POSTFIX;
            }

            return t;
        }

        /**
         * This is the central entry point for Paste and DnD Drop
         *
         * @param p_externalObject
         * @return
         */
        public boolean handleExternalDataInput(ExternalObject p_externalObject) {
            Object d = null;

            if (p_externalObject.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) {
                try {
                    d = p_externalObject.getData(DataFlavor.javaFileListFlavor);
                } catch (UnsupportedFlavorException use) {
                    if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                        LOG.log(Level.SEVERE, "Caught unexpected Exception", use);
                    }
                }
            } else if (p_externalObject.isDataFlavorSupported(DataFlavor.stringFlavor)) {
                try {
                    d = p_externalObject.getData(DataFlavor.stringFlavor);
                } catch (UnsupportedFlavorException use) {
                    if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                        LOG.log(Level.SEVERE, "Caught unexpected Exception", use);
                    }
                }
            }
            boolean ownHandlingSuccess = handle(d);
            if (!ownHandlingSuccess && p_externalObject.getTransferable() != null) {
                return editor.getTransferHandler().importData(editor, p_externalObject.getTransferable());
            } else {
                return true;
            }
        }
    }
}
